package com.facedemo.face;

import android.graphics.Bitmap;
import android.graphics.Rect;
import android.hardware.Camera;
import android.view.SurfaceView;
import android.widget.RelativeLayout;

import com.facedemo.MyApplication;
import com.facedemo.bean.DCamera;
import com.facedemo.bean.FaceFeature;
import com.facedemo.bean.FaceTraceInfo;
import com.facedemo.bean.LiveStatus;
import com.facedemo.db.entity.Person;
import com.facedemo.db.utils.PersonDaoUtils;
import com.facedemo.face.callback.FaceCallback;
import com.facedemo.utils.ConvertDataInfo;
import com.facedemo.utils.DisplayUtil;
import com.facedemo.utils.LogUtils;
import com.facedemo.utils.ReuseThread;
import com.facedemo.view.CameraSurface;
import com.facedemo.view.FaceMarkSurface;

import net.facelib.live.FaceLiveAndroid;
import net.facelib.mtfsdk.FaceApiMtfV2Android;
import net.facelib.mtfsdk.NativeFaceInfo;
import net.gdface.sdk.CodeInfo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

//人脸识别工具类
public class FaceDetectUtil implements Camera.PreviewCallback,CameraSurface.SurfaceViewCallback {
    private FaceConfig faceConfig;//人脸配置
    private DCamera cameraConfig;//摄像头配置

    private Camera mCamera;
    private CameraSurface cameraSurface;
    private FaceMarkSurface faceRectView;

    private boolean startFaceRun = false;//开始人脸识别
    private FaceCallback faceCallback;
    private ConvertDataInfo convertDataInfo;

    public FaceDetectUtil(FaceConfig faceConfig, CameraSurface cameraSurface, FaceMarkSurface faceMarkSurface) {
        this.faceConfig = faceConfig;
        cameraConfig = MyApplication.dCamera;

        convertDataInfo = new ConvertDataInfo(cameraConfig.cameraDataRotate,
                cameraConfig.cameraWidth,
                cameraConfig.cameraHeight);

        setCameraSurface(cameraSurface);
        setFaceMarkSurfaceView(faceMarkSurface);
        recognizeThread = new FaceRecognizeThread();
    }

    public void setFaceCallback(FaceCallback faceCallback) {
        this.faceCallback = faceCallback;
    }

    private void setCameraSurface(CameraSurface cameraSurface) {
        this.cameraSurface = cameraSurface;
        cameraSurface.setSurfaceCallback(this);

        this.cameraSurface.post(new Runnable() {
            @Override
            public void run() {
                updateSurfaceSize(FaceDetectUtil.this.cameraSurface);
            }
        });
    }

    private void setFaceMarkSurfaceView (FaceMarkSurface faceMarkSurface) {
        this.faceRectView = faceMarkSurface;
        this.faceRectView.post(new Runnable() {
            @Override
            public void run() {
                updateSurfaceSize(FaceDetectUtil.this.faceRectView);
            }
        });
    }

    private void updateSurfaceSize(SurfaceView surfaceView) {
        if(surfaceView==null) return;

        int cameraW = cameraConfig.cameraWidth;
        int cameraH = cameraConfig.cameraHeight;
        if(cameraConfig.displayOrientation==90 || cameraConfig.displayOrientation==270) {
            cameraW = cameraConfig.cameraHeight;
            cameraH = cameraConfig.cameraWidth;
        }

        float cameraScale = (float)cameraW/(float)cameraH;
        RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)surfaceView.getLayoutParams();
        lp.width = DisplayUtil.getWindowWidth();
        lp.height = (int)(lp.width  / cameraScale);
        surfaceView.setLayoutParams(lp);
    }

    public boolean isStartFaceRun() {
        return startFaceRun;
    }

    public void setStartFaceRun(boolean startFaceRun) {
        this.startFaceRun = startFaceRun;
    }

    @Override
    public void surfaceCreated() {
        mCamera = Camera.open(cameraConfig.cameraId);
        Camera.Parameters parameters = mCamera.getParameters();

        boolean isPreviewSizeRight = false;
        List<Camera.Size> list = parameters.getSupportedPreviewSizes();
        for(int i=0;i<list.size();i++) {
            Camera.Size size = list.get(i);
            LogUtils.printD("camear parameters w:"+size.width+" h:"+size.height);
            if(size.width==cameraConfig.cameraWidth && size.height==cameraConfig.cameraHeight) {
                isPreviewSizeRight = true;
            }
        }

        if(isPreviewSizeRight) {
            parameters.setPreviewSize(cameraConfig.cameraWidth, cameraConfig.cameraHeight);
            mCamera.setParameters(parameters);

            try {
                mCamera.setDisplayOrientation(cameraConfig.displayOrientation);
                mCamera.setPreviewCallback(this);
                mCamera.setPreviewDisplay(cameraSurface.getCameraSufaceHolder());
                mCamera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
                mCamera = null;
            }
        } else {
            LogUtils.printE("请正确设置摄像头分辨率");
            if (mCamera!=null) {
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview();
                mCamera.release();
                mCamera = null;
            }
        }
    }

    @Override
    public void surfaceDestroyed() {
        if (mCamera!=null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    private long detectTime;
    private boolean skipFrame = true;//跳帧

    private List<CodeInfo> faceInfos = new ArrayList<>();;//检测结果
    private List<FaceTraceInfo> faceTraceInfos = new ArrayList<>();;//人脸追踪信息

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if(!startFaceRun || !FaceLicenseManger.isVaild) return;
        //跳帧
        if(skipFrame) {
            skipFrame = false;
            return;
        }
        skipFrame = true;

        //图像数据转换
        convertDataInfo.convert(data,true);

        faceInfos.clear();

        if(LogUtils.isCountFaceTime) { detectTime = System.currentTimeMillis();}
        if(faceConfig.getFaceDetectType() == FaceConfig.DETECT_TYPE_MAX) {
            //单人脸检测
            NativeFaceInfo faceDetectResult = FaceApiMtfV2Android.getInstance().nativeDetectMaxFace(convertDataInfo.getDataBGR(),convertDataInfo.getWidthBGR(),convertDataInfo.getHeightBGR(),1);
            if(faceDetectResult!=null) faceInfos.add(faceDetectResult);

        } else {
            //多人脸检测
            FaceApiMtfV2Android.getInstance().nativeDetectFace(convertDataInfo.getDataBGR(),convertDataInfo.getWidthBGR(),convertDataInfo.getHeightBGR(),faceInfos,1);
        }
        if(LogUtils.isCountFaceTime && faceInfos.size()>0) { LogUtils.printD("人脸数："+faceInfos.size()+" ("+faceConfig.getFaceDetectType()+")检测用时："+(System.currentTimeMillis() - detectTime));}

        //限制最小人脸
        int detectMinFacePix = (int)(convertDataInfo.getWidthBGR() * faceConfig.getMinFaceIod());
        if(faceInfos.size()>0) {
            for(int i=0;i<faceInfos.size();i++) {
                if(faceInfos.get(i).getPos().getWidth() < detectMinFacePix) {
                    faceInfos.remove(i);
                    i--;
                }
            }
        }

        //没有人脸，清空信息
        if(faceInfos.size()==0) {
            if(faceRectView!=null)
                faceRectView.clearDrawInfo();

            if(faceTraceInfos.size()>0)
                faceTraceInfos.clear();

            if (null != convertDataInfo.getBitmapRGB() && !convertDataInfo.getBitmapRGB().isRecycled()) {
                convertDataInfo.getBitmapRGB().recycle();
            }
            return;
        }

        //更新人脸追踪列表( 此列表的增删操作只会在当前线程进行，不需要加线程锁)
        long lastTraceTime = System.currentTimeMillis();
        for(CodeInfo codeInfo:faceInfos) {
            FaceTraceInfo ft = findAndUpdateTheSameTrace(faceTraceInfos,codeInfo,lastTraceTime);
            if(ft==null) {//添加新追踪
                ft = new FaceTraceInfo(codeInfo,convertDataInfo.getWidthBGR(), convertDataInfo.getHeightBGR(),lastTraceTime);
                faceTraceInfos.add(ft);
            }
        }

        //移除失败的追踪
        for(int i=0;i<faceTraceInfos.size();i++) {
            if(faceTraceInfos.get(i).getLastTraceTime() != lastTraceTime) {
                faceTraceInfos.remove(i);
                i--;
            }
        }

        //绘制人脸框
        if(faceInfos.size()>0) {
            if(faceRectView!=null)
                faceRectView.drawInfo(faceTraceInfos,!faceConfig.isDuplicateRecognition());
        }

        if (!recognizeThread.nowRuning()) {//识别线程有空

            //如果此人脸未被识别 或 配置了重复识别，就添加到列表中
            List<FaceTraceInfo> neetToRecognizeFace = null;
            List<CodeInfo> neetToRecognizeCodeInfo = null;
            for(FaceTraceInfo ft:faceTraceInfos) {
                synchronized (ft) {
                    if (ft.getRecognizeStatus() == FaceTraceInfo.STATUS_WAIT || faceConfig.isDuplicateRecognition()) {
                        if (neetToRecognizeFace == null) neetToRecognizeFace = new ArrayList<>();
                        if(neetToRecognizeCodeInfo == null) neetToRecognizeCodeInfo = new ArrayList<>();

                        //把人脸追踪信息标记为 正在识别
                        ft.setRecognizeStatus(FaceTraceInfo.STATUS_FACE_RECOGNIZE);
                        neetToRecognizeFace.add(ft);
                        neetToRecognizeCodeInfo.add(ft.getCodeInfo());
                    }
                }
            }

            if(neetToRecognizeCodeInfo !=null && neetToRecognizeCodeInfo.size()>0) {
                //线程中进行人脸识别
                recognizeThread.setFaceData(neetToRecognizeFace,neetToRecognizeCodeInfo,
                        convertDataInfo.getDataBGR(),convertDataInfo.getWidthBGR(), convertDataInfo.getHeightBGR(),
                        convertDataInfo.getBitmapRGB());
                recognizeThread.startThread();
                return;
            }
        }
        convertDataInfo.recycleRgbBitmap();
    }

    //根据新的人脸检测结果，从上一次的 人脸追踪列表中 找到对应的FaceTraceInfo，并更新FaceTraceInfo的相关人脸检测信息
    public FaceTraceInfo findAndUpdateTheSameTrace(List<FaceTraceInfo> faceTraceInfos,CodeInfo codeInfo, long traceTimeTag){
        if(codeInfo == null) return null;

        Rect newRect = new Rect(codeInfo.getPos().getLeft(),
                codeInfo.getPos().getTop(),
                codeInfo.getPos().getLeft() + codeInfo.getPos().getWidth(),
                codeInfo.getPos().getTop() + codeInfo.getPos().getHeight());

        double centerX =  newRect.left + (newRect.right - newRect.left)/(double)2 ;
        double centerY = newRect.top + (newRect.bottom - newRect.top)/(double)2;

        double minJuli = 100000;
        FaceTraceInfo minFaceTraceInfo = null;
        for(FaceTraceInfo ftInfo:faceTraceInfos) {
            double preCenterX = ftInfo.getRect().left + (ftInfo.getRect().right - ftInfo.getRect().left)/(double)2 ;
            double preCenterY = ftInfo.getRect().top + (ftInfo.getRect().bottom - ftInfo.getRect().top)/(double)2;
            //两个中心点的距离
            double juli = Math.sqrt((centerX - preCenterX)* (centerX - preCenterX)+(centerY - preCenterY)* (centerY - preCenterY));
            if(juli<minJuli){
                minJuli = juli;
                minFaceTraceInfo = ftInfo;
            }
        }
        if(minFaceTraceInfo!=null) {
            //人脸宽度1/4
            double width4 = (newRect.right - newRect.left)/4;
            if(minJuli<width4) {
                minFaceTraceInfo.updateTraceInfo(codeInfo,newRect,traceTimeTag);
                return minFaceTraceInfo;
            }
        }
        return null;
    }


    //===========================================人脸识别线程
    private FaceRecognizeThread recognizeThread;
    private class FaceRecognizeThread extends ReuseThread {
        private List<FaceTraceInfo> faceInfos;
        private List<CodeInfo> codeInfos;


        private byte[] data_BGR;
        private int picW = 0;
        private int picH = 0;
        private Bitmap bitmapRGB;

        private long liveTime,featureTime,comparisonTime;

        /**
         * 设置人脸识别数据
         * @param faceInfos 人脸追踪结果
         * @param codeInfos 人脸检测结果
         * @param dataBgr 用于提取人脸特征的 BGR 图像（方法内会拷贝）
         * @param picW 图像宽度
         * @param picH 图像高度
         * @param bitmapRGB RGB 图像 如果不需要，可传空
         * */
        public void setFaceData(List<FaceTraceInfo> faceInfos,List<CodeInfo> codeInfos, byte[] dataBgr, int picW, int picH, Bitmap bitmapRGB){
            if(!isRuning) {
                if(dataBgr!=null) {
                    if(this.data_BGR==null) this.data_BGR = new byte[dataBgr.length];
                    System.arraycopy(dataBgr, 0, this.data_BGR, 0, this.data_BGR.length);
                } else {
                    this.data_BGR = null;
                }
                this.faceInfos = faceInfos;
                this.codeInfos = codeInfos;
                this.picW = picW;
                this.picH = picH;
                this.bitmapRGB = bitmapRGB;
            }
        }

        @Override
        public void work() {
            liveTime = 0;
            featureTime = 0;
            comparisonTime = 0;

            //是否使用活体检测
            if(faceConfig.isUseLiveProcess()) {
                for(int i=0;i<faceInfos.size();i++) {
                    if(LogUtils.isCountFaceTime) { liveTime = System.currentTimeMillis();}
                    double[] nativeData = ((NativeFaceInfo)codeInfos.get(i)).getNativeData().clone();
                    if(faceConfig.getLiveLevel() == 2) {
                        nativeData[nativeData.length-1] = 2; //提高活体安全等级
                    } else {
                        nativeData[nativeData.length-1] = 1;
                    }
                    int liveResult =FaceLiveAndroid.getInstance().nativeLiveProcess(data_BGR,picW,picH,nativeData);
                    if(LogUtils.isCountFaceTime) { liveTime = System.currentTimeMillis() - liveTime;}
                    if(0==liveResult) {
                        LogUtils.printD("FA==》非活体 LiveLevel："+faceConfig.getLiveLevel()+" 用时："+liveTime);
                        faceInfos.get(i).liveStatus = LiveStatus.NO;
                        recognizeFailed(faceInfos.get(i));

                        faceInfos.remove(i);
                        codeInfos.remove(i);
                        i--;
                    } else {
                        faceInfos.get(i).liveStatus = LiveStatus.YES;
                    }
                }
            }

            if(codeInfos.size()==0) {
                return;
            }

            //提取人脸特征
            if(faceConfig.getFaceDetectType() == FaceConfig.DETECT_TYPE_MAX) { //单人脸
                if(LogUtils.isCountFaceTime) { featureTime = System.currentTimeMillis();}
                byte[] feaA = FaceApiMtfV2Android.getInstance().nativeGetFaceFeature(data_BGR,picW,picH,codeInfos.get(0));
                if(LogUtils.isCountFaceTime) { featureTime = System.currentTimeMillis() - featureTime;}

                //与数据库中的人脸特征做比对
                String personId = faceComparison(feaA);
                if(personId!=null) recognizeSuccess(faceInfos.get(0),personId);
                else recognizeFailed(faceInfos.get(0));

            } else {//多人脸
                if(LogUtils.isCountFaceTime) { featureTime = System.currentTimeMillis();}
                byte[][] features = FaceApiMtfV2Android.getInstance().nativeGetFaceFeatures(data_BGR,picW,picH,codeInfos);
                if(LogUtils.isCountFaceTime) {featureTime = System.currentTimeMillis() - featureTime;}

                for (int i = 0; i < features.length; i++) {
                    //与数据库中的人脸特征做比对
                    String personId = faceComparison(features[i]);
                    if(personId!=null) recognizeSuccess(faceInfos.get(i),personId);
                    else recognizeFailed(faceInfos.get(i));
                }
            }

        }

        //与数据库中的人脸特征做比对
        private String faceComparison(byte[] feaA) {
            if(feaA==null) return null;

            double max_similarity = 0;
            String personId = null;
            synchronized (FaceInfoManger.getInstance().featureListLock) {
                if(LogUtils.isCountFaceTime) { comparisonTime = System.currentTimeMillis();}
                for (FaceFeature item : FaceInfoManger.getInstance().getFeatureList()) {
                    if (null != item.feature) {
                        double similarity = FaceApiMtfV2Android.getInstance().compareCode(item.feature, feaA);
                        if (similarity > max_similarity) {
                            max_similarity = similarity;
                            personId = item.personId;
                        }
                    }
                }
                if(LogUtils.isCountFaceTime) { comparisonTime = System.currentTimeMillis() - comparisonTime;}
            }

            if (max_similarity > 0 && Double.doubleToLongBits(max_similarity) > Double.doubleToLongBits(faceConfig.getmLightFZ())) {//识别通过
                LogUtils.printI("FA==》识别成功 人员："+personId+" similarity:"+max_similarity);
                return personId;

            } else {
                LogUtils.printI("FA==》不通过 similarity:"+max_similarity);
                return null;
            }
        }

        //识别成功
        private void recognizeSuccess(FaceTraceInfo faceInfo, String id) {
            if(LogUtils.isCountFaceTime) { LogUtils.printD("FA==》活体用时："+liveTime+" 特征用时："+featureTime+" 对比用时："+comparisonTime);}
            //更新人脸追踪结果
            String name = null;
            if(!faceConfig.isDuplicateRecognition()) {
                Person p = PersonDaoUtils.queryById(id);
                if(p!=null) name = p.getName();
            }
            synchronized (faceInfo) {
                faceInfo.setDetectionSuccess(id,name);
            }

            //结果回调
            if(faceCallback!=null) {
                faceCallback.detectionSuccess(id);
            }

            if (null != bitmapRGB && !bitmapRGB.isRecycled()) {
                bitmapRGB.recycle();
                bitmapRGB = null;
            }
        }

        //识别失败
        private void recognizeFailed(FaceTraceInfo faceInfo) {
            int filedNum = 1;

            //更新人脸追踪结果
            synchronized (faceInfo) {
                faceInfo.setDetectionFailed();
                filedNum = faceInfo.getRecognizeNum();
            }

            //结果回调
            if(faceCallback!=null) {
                faceCallback.detectionFailed(filedNum);
            }

            if (null != bitmapRGB && !bitmapRGB.isRecycled()) {
                bitmapRGB.recycle();
                bitmapRGB = null;
            }
        }
    };

    public void destory() {
        startFaceRun = false;
        if(recognizeThread!=null) {
            recognizeThread.stopThread();
            recognizeThread = null;
        }
    }
}
